2014_safdie_habitat_67.py

#

SPDX-FileCopyrightText: 2014 Nitay Lehrer SPDX-FileCopyrightText: 2024 AlICe laboratory https://alicelab.be

SPDX-License-Identifier: GPL-3.0-or-later

#

HABITAT 67’ script - Nitay Lehrer - 22/01/2015 Created in blender hash 9337574, OS: Microsoft Windows 7.0

#

For script to work, PIVOT CENTER needs to be set on “MEDIAN POINT” on both OBJECT and EDIT modes# All PRINT commands are for debugging purposes, and their output is erased before the final output. To enable PRINT outputs, place “#” before os.system(“cls”) , at the very end of the script.

#

Habitat ‘67 - Bound development

import bpy
import random
import bpy, bmesh
from mathutils import Vector
import os
#

#

Erasing all previously generated objects

bpy.context.scene.layers[0] = False
bpy.context.scene.layers[19] = True
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)

bpy.context.scene.layers[0] = True
bpy.context.scene.layers[19] = False
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
#

#
        #---------------------------------------------------------------#
        #                   MAKING THE RANDOM PERIMITER                 #
        #---------------------------------------------------------------#
#

CREATING A SERIES OF INTERLOCKING BOXES

def myBox(pos_X, pos_Y, pos_Z, dim_X, dim_Y, dim_Z):
    bpy.ops.mesh.primitive_cube_add(location=(pos_X, pos_Y, pos_Z))
    bpy.ops.transform.resize(value=(0.5, 0.5, 0.5))
    bpy.ops.transform.resize(value=(dim_X, dim_Y, dim_Z))


array_X = random.randint(1, 3)
array_Y = random.randint(1, 3)


for j in range(0, array_Y):
    for i in range(0, array_X):
        scale = random.randint(1, 5)
        myBox(i, j, -0.02, random.uniform(0.5, 3), random.uniform(1, 2), 0.01)
#

JOINING THE DIFFERENT GOUND PLANES AND CENTERING THEM

bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.join()  # joining the boxes into one geometry
bpy.context.object.name = "BASE"  # Changing the name
bpy.context.object.data.name = "BASE"
bpy.data.objects["BASE"].bound_box
bpy.ops.object.origin_set(
    type="ORIGIN_CENTER_OF_MASS"
)  # changing the origin of the base plane to the center of mass
bpy.context.object.location[0] = 0  # centering the base plane in the X Y axis
bpy.context.object.location[1] = 0
bpy.context.object.location[2] = -0.01
#

#
        #--------------------------------------------------------------------#
        #                      DEFINING ALL FUNCTIONS                        #
        #--------------------------------------------------------------------#

====================================================================================================

CREATING ALL THE LISTS WE WILL USE IN THE SCRIPT:

              stack = All the modules used in the process
      lastDirection = The last written direction of the module
      nextDirection = Once reaching an edge, the next module direction according to the orientation of the edge
              elbow = The next elbow to be placed according to the last and next directions
        placedElbow = Verification that the last placed elbow is the correct one
               cube = The bounding box of the current module
     iterationCount = A registry of the amount of times a module has been replicated in a wing
     coorObjectMode = Coordinates of the center of the bounding box in object mode, used in edgeAnalysis()
       coorEditMode = Coordinates of the center of the bounding box in edit mode, also used in edgeAnalysis()
   edgeAnalysisList = A registry of the last orientations of an edge hit by a wing - either "Ver"(tical) or "Hor"(izontal)
stack = []
lastDirection = []
nextDirection = []
elbow = []
placedElbow = []
cube = []
iterationCount = []
coorObjectMode = []
coorEditMode = []
edgeAnalysisList = []
#

====================================================================================================

THE FUNCTION THAT STARTS THE PROCESS DebName = The predefined name of the start module to look for in layer 1. The name corresponds to the beginning direction. Modname = The predefined name of the first drectional module to look for in layer 2. The name corresponds to the beginning direction. Direction = The Random beginning direction — NW= North-West NE= North-East SE= South-East SW= South-West trans_X = The X manipulation of the module in the chosen direction trans_Y = The Y manipulation of the module in the chosen direction

#
def START(DebName, ModName, Direction, trans_X, trans_Y):
#

Selecting and duplicating the start module

    bpy.ops.object.select_all(action="TOGGLE")
    bpy.context.scene.layers[1] = True
    object = bpy.data.objects[DebName]
    object.select = True
    bpy.context.scene.objects.active = bpy.data.objects[DebName]
    bpy.ops.object.duplicate()
    bpy.context.scene.objects.active = bpy.data.objects[DebName + ".001"]
#

Moving the start module into position in layer 0 and turning off layer 1. Changing the name to correspond to the direction.

    bpy.ops.object.move_to_layer(
        layers=(
            True,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l
    bpy.context.scene.layers[1] = False
    bpy.context.object.name = "Deb" + str(Direction)
    bpy.context.object.data.name = "Deb" + str(Direction)
    bpy.ops.object.select_all(action="TOGGLE")
#

Moving the first directional module into position in layer 0 and turning off layer 2. Changing the name to correspond to the direction.

    bpy.context.scene.layers[2] = True
    object2 = bpy.data.objects[ModName]
    object2.select = True
    bpy.context.scene.objects.active = bpy.data.objects[ModName]
    bpy.ops.object.duplicate()
    bpy.context.scene.objects.active = bpy.data.objects[ModName + ".001"]
    bpy.ops.object.move_to_layer(
        layers=(
            True,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l
    bpy.context.scene.layers[2] = False
    bpy.context.object.name = "MOD" + str(Direction)
    bpy.context.object.data.name = "MOD" + str(Direction)
#

Placing the module correctly according to the start module

    bpy.context.active_object.location += Vector([trans_X, trans_Y, 0])
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
    stack.append(
        bpy.context.scene.objects.active
    )  # Adding the new module as the first in the list of directional modules ( stack[] )
#

====================================================================================================

BUILDING THE BOUNDING BOX OF EACH MODULE, THEN INTERSECTING IT WITH THE BASE PLANE. THIS WILL TELL US IF THE MODULE HASE REACHED AN EDGE. According to the direction of the wing and the beginning position of the process, the box will be moved differently. DirectionInt = An integer corresponding to the random initial direction. 0=NW 1=NE 2=SE 3=SW translateBox = The set of vector coordinates that wil govern the displacement of the box, so that it perfectly encompasses the module.

#
def boxInter2(DirectionInt):
#

Defining the displacement of the box

    if (startingPos == 0 or startingPos == 2) and DirectionInt == 0:
        translateBox = (0.02, -0.2264, 0)
    elif (startingPos == 1 or startingPos == 3) and DirectionInt == 0:
        translateBox = (-0.02, 0.2264, 0)
    elif (startingPos == 0 or startingPos == 2) and DirectionInt == 1:
        translateBox = (0.02, 0.2264, 0)
    elif (startingPos == 1 or startingPos == 3) and DirectionInt == 1:
        translateBox = (-0.02, -0.2264, 0)
    elif (startingPos == 0 or startingPos == 2) and DirectionInt == 2:
        translateBox = (-0.02, 0.2264, 0)
    elif (startingPos == 1 or startingPos == 3) and DirectionInt == 2:
        translateBox = (0.02, -0.2264, 0)
    elif (startingPos == 0 or startingPos == 2) and DirectionInt == 3:
        translateBox = (-0.02, -0.2264, 0)
    elif (startingPos == 1 or startingPos == 3) and DirectionInt == 3:
        translateBox = (0.02, 0.2264, 0)
#

Creating the box and displacing it.

    bpy.ops.mesh.primitive_cube_add(
        view_align=False,
        enter_editmode=False,
        location=(0, 0, 0),
    )
    bpy.ops.transform.resize(value=(0.1265, 0.2265, 0.5))
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_selected_to_cursor(
        use_offset=False
    )  # displacing the center of the box to the cursor
    bpy.context.area.type = "TEXT_EDITOR"
    bpy.ops.transform.translate(
        value=translateBox
    )  # final displacement of the box to fit to module
#

Intersecting the box with the base plane

    bpy.ops.object.modifier_add(
        type="BOOLEAN"
    )  # Boolean difference operation on base plane
    bpy.context.object.modifiers["Boolean"].operation = "DIFFERENCE"
    bpy.context.object.modifiers["Boolean"].object = bpy.data.objects["BASE"]
    bpy.ops.object.modifier_apply(
        apply_as="DATA", modifier="Boolean"
    )  # applying the boolean to create a hard coded form
    bpy.ops.object.modifier_add(
        type="DECIMATE"
    )  # "Decimate" modifier on the remaining geometry
    bpy.context.object.modifiers["Decimate"].decimate_type = (
        "DISSOLVE"  # This merges all coplanar faces, so as not to give false readings in the face count
    )
    bpy.ops.object.modifier_apply(apply_as="DATA", modifier="Decimate")
    result = bpy.context.object
    count = len(result.data.polygons)  # Counting the amount of faces
    edges = len(result.data.edges)  # Counting the amount of vertices
    cube.append(
        bpy.context.scene.objects.active
    )  # Adding the cube to the list of cubes

    print("___1___the number of faces is", str(count))
    print("___2___the number of edges is", str(edges))
    print("___3___the last cube is", cube[-1])
#

Only a box that is perfectly on top of the base plane will have 12 FACES and 24 VERTICES, anything else means that the box has reached or transgressed an edge.

#

====================================================================================================

CREATING A NEW WING OF MODULES

Based on the first directional module, the function copies it until it reaches the edge of the island.

  Direction = The required direction of the wing
    trans_X = Translation of the copied modules in the X axis
    trans_Y = Translation of the modules on the Y axis

DirectionInt = The direction integer fed into the boxInter2() function x = The maximum number of modules in a wing

#
def wing(Direction, trans_X, trans_Y, DirectionInt):
    m = 0
    x = 12
    while m <= x:

        print(
            "___4___the last object in stack before wing in iteration",
            m + 1,
            "is",
            stack[-1],
        )
#

Analysing the intersected box, left over from boxInter2(), to discover the orientation of the reached edge.

        def edgeAnalysisForWing():

            A = (
                bpy.context.object.location
            )  # geometric coordinates of the center of the box
            bpy.ops.object.editmode_toggle()  # going into edit mode, which moves the center of gravity
            bpy.context.area.type = "VIEW_3D"
            bpy.ops.view3d.snap_cursor_to_selected()  # moving cursor to new center of the object.
            bpy.context.area.type = "TEXT_EDITOR"
            B = (
                bpy.context.scene.cursor_location
            )  # retreiving the coordinates of the cursor
            bpy.ops.object.editmode_toggle()

            coorObjectMode.append(A)
            coorEditMode.append(B)

            print(A)
            print(B)
            A = [abs(i) for i in A]  # absolute value of coordinates
            B = [abs(i) for i in B]

            C = [b - a for a, b in zip(A, B)]  # Subtracting one vector from the other
            C = [abs(i) for i in C]

            print(C)

            if C[0] > C[1]:  # if the result in X is bigger than in Y, the edge is N/S
                edgeAnalysisList.append("Ver")
                print("___5___the edge is vertical")
            else:  # if the result is bigger in Y than in X, the edge is E/W
                edgeAnalysisList.append("Hor")
                print("___5___the edge is horizontal")
#

All necessary action before reiterating Wing() : First, the bounding box is stocked in layer 19, for debugging purposes. Then, the last module is selected again, to be duplicated in the next iteration

        def nextIteration():
            bpy.ops.object.move_to_layer(
                layers=(
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    False,
                    True,
                )
            )
            l = [False] * 20
            l[0] = True
            bpy.context.scene.layers = l
            bpy.context.scene.layers[19] = False
            bpy.ops.object.select_all(action="SELECT")
            bpy.ops.object.select_all(action="TOGGLE")
            print("___6___the last object in stack2 is", stack[-1])
            stack[-1].select = True
            bpy.context.scene.objects.active = stack[-1]
            print("___7___the last object is", bpy.context.scene.objects.active)
#

Adding the last direction to the list of last directions

        if DirectionInt == 0:
            lastDirection.append("NW")
        elif DirectionInt == 1:
            lastDirection.append("NE")
        elif DirectionInt == 2:
            lastDirection.append("SE")
        elif DirectionInt == 3:
            lastDirection.append("SW")
#

Duplicating and displacing the next module

        bpy.ops.object.duplicate_move(
            OBJECT_OT_duplicate={"mode": "TRANSLATION"},
            TRANSFORM_OT_translate={"value": (trans_X, trans_Y, 0)},
        )

        stack.append(
            bpy.context.scene.objects.active
        )  # appending the module to the list

        print(
            "___8___--THE NEXT OBJECT IN THE WING IS ---",
            bpy.context.scene.objects.active,
        )
#

Moving the cursor to the origin of the next module, so that the bounding box can snap to it

        bpy.context.area.type = "VIEW_3D"
        bpy.ops.view3d.snap_cursor_to_selected()
        bpy.context.area.type = "TEXT_EDITOR"
#

Applying the function to check if the module is on the base plane

        boxInter2(DirectionInt)

        print("___9___The bounding box is", bpy.context.scene.objects.active)
#

If a bounding box is on an edge or off the base plane, investigate the details

        if (len(bpy.context.object.data.polygons)) != 12 or (
            len(bpy.context.object.data.edges)
        ) != 24:
            edgeAnalysisForWing()
            x1 = coorObjectMode[-1][
                0
            ]  # The x coordinate of the last bounding box in object mode
            x2 = coorEditMode[-1][
                0
            ]  # The x coordinate of the last bounding box in edit mode
            y1 = coorObjectMode[-1][
                1
            ]  # The y coordinate of the last bounding box in object mode
            y2 = coorEditMode[-1][
                1
            ]  # The y coordinate of the last bounding box in object mode

            print(
                "___10___the coordinates are: x1=", x1, "x2=", x2, "y1=", y1, "y2=", y2
            )

            if (len(bpy.context.object.data.polygons)) == 6 or (
                len(bpy.context.object.data.edges)
            ) == 12:  # the module is totally off the base plane. Stop the wing() function.
                iterationCount.append(m)
                print(
                    "\n\t\t\t\t\t after",
                    m + 1,
                    "iterations, the operation has reached abbys, the last direction was",
                    DirectionInt,
                    "\n",
                )
                break
            elif (
                (lastDirection[-1] == "SW" or lastDirection[-1] == "SE")
                and (y1 > (y2 + 0.0001))
                and edgeAnalysisList[-1] == "Hor"
            ):  # the wing started outside the base plane and wants to go back. It shouldn't be stopped by an edge
                print(
                    "\n\t\t\t\t\t discovered an edge but skipped it, the edge was",
                    edgeAnalysisList[-1],
                    "\n",
                )
                nextIteration()
                m = m + 1
            elif (
                (lastDirection[-1] == "NW" or lastDirection[-1] == "NE")
                and (y1 < (y2 - 0.0001))
                and edgeAnalysisList[-1] == "Hor"
            ):  # the wing started outside the base plane and wants to go back. It shouldn't be stopped by an edge
                print(
                    "\n\t\t\t\t\t discovered an edge but skipped it, the edge was",
                    edgeAnalysisList[-1],
                    "\n",
                )
                nextIteration()
                m = m + 1
            elif (
                (lastDirection[-1] == "NE" or lastDirection[-1] == "SE")
                and (x1 < (x2 + 0.0001))
                and edgeAnalysisList[-1] == "Ver"
            ):  # the wing started outside the base plane and wants to go back. It shouldn't be stopped by an edge
                print(
                    "\n\t\t\t\t\t discovered an edge but skipped it, the edge was",
                    edgeAnalysisList[-1],
                    "\n",
                )
                nextIteration()
                m = m + 1
            elif (
                (lastDirection[-1] == "NW" or lastDirection[-1] == "SW")
                and (x1 > (x2 - 0.0001))
                and edgeAnalysisList[-1] == "Ver"
            ):  # the wing started outside the base plane and wants to go back. It shouldn't be stopped by an edge
                print(
                    "\n\t\t\t\t\t discovered an edge but skipped it, the edge was",
                    edgeAnalysisList[-1],
                    "\n",
                )
                nextIteration()
                m = m + 1
            else:  # The wing has reached an edge from the inside. wing() will be stopped.
                iterationCount.append(m)
                print(
                    "\n\t\t\t\t\t after",
                    m + 1,
                    "iterations, the operation reached the edge, the last direction was",
                    DirectionInt,
                    "\n",
                )
                break
#

If the wing arrives to its last iteration, it has to prepare an ending for the next function

        else:
            if m == x:
                iterationCount.append(m)
                print(
                    "\n\t\t\t\t\t after",
                    m + 1,
                    "iterations, the operation reached the abbys, the last direction was",
                    DirectionInt,
                    "\n",
                )
                break
            else:
                nextIteration()
                m = m + 1
#
def moveToLayer19():
    bpy.ops.object.move_to_layer(
        layers=(
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            True,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l
    bpy.context.scene.layers[19] = False
#

====================================================================================================

A FURTHER ANALYSIS OF INTERSECTION BETWEEN BOUNDING BOX AND BASE PLANE

This time, it choses which elbow to place after the last wing, according to the last direction, the orientation of the edge, and therefore the next direction

#
def edgeAnalysis():

    A = bpy.context.object.location  # geometric coordinates of the center of the box
    bpy.ops.object.editmode_toggle()  # going into edit mode, which moves the center of gravity
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()  # moving cursor to new center of the object.
    bpy.context.area.type = "TEXT_EDITOR"
    B = bpy.context.scene.cursor_location  # retreiving the coordinates of the cursor
    bpy.ops.object.editmode_toggle()

    coorObjectMode.append(A)
    coorEditMode.append(B)

    print(A)
    print(B)
    A = [abs(i) for i in A]  # absolute value of coordinates
    B = [abs(i) for i in B]

    C = [b - a for a, b in zip(A, B)]  # Subtracting one vector from the other
    C = [abs(i) for i in C]
    print(C)

    if C[0] > C[1]:  # if the result in X is bigger than in Y, the edge is N/S
        print("___9___the edge is vertical")
    else:  # if the result is bigger in Y than in X, the edge is E/W
        print("___9___the edge is horizontal")
#

Erasing modules from the end of the wing to make space for the elbow. The length of the wing determines how many modules will be erased, if at all.

    print(
        "___10___The box to be MOVED TO LAYER 20 is", bpy.context.scene.objects.active
    )
    moveToLayer19()

    if iterationCount[-1] >= 2:
        print("___11___the object to be deleted is", stack[-1])
        stack[-1].select = True
        bpy.ops.object.delete(use_global=False)
        stack.pop()  # removing the last module from the list, now we will have a new last module
        print("___12___the next object to be deleted is", stack[-1])
        stack[-1].select = True
        bpy.ops.object.delete(use_global=False)
        stack.pop()
        print("___13___after deleting, the last object is", stack[-1])
    elif iterationCount[-1] == 1:
        print("___14___the only object to be deleted is", stack[-1])
        stack[-1].select = True
        bpy.ops.object.delete(use_global=False)
        stack.pop()
    else:
        print("___15___NO deleting, the last object is STILL", stack[-1])

    stack[-1].select = True  # selecting the new last module
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
    print("___14___The very last direction is", lastDirection[-1])
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
#

According to the last know direction and the direction of barrier edge, we can choose our elbow If the module reaches a corner, it is cut in two areas. In this case, the center of mass will move more, paradoxically, towards the side that was cut less(since there is suddenly a dense collection of vertices).Therefore, if the box is cut by a corner (which means that both x and y of the center of mass move), the displacement is transformed by ^-1 so that it reflects the more important edge

If the edge is Horizontal, the direction is flipped horizontally. If vertical, then vertically. Then, the next direction is appended to the list, as well as the chosen elbow

    if C[0] > 0.001 and C[1] > 0.001:

        print("\n\n*****************the elbow is on a corner**********************\n\n")

        if lastDirection[-1] == "NW" and (1 / C[0]) > (1 / C[1]):
            nextDirection.append("NE")
            elbow.append("NW2NE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NW" and (1 / C[0]) < (1 / C[1]):
            nextDirection.append("SW")
            elbow.append("NW2SW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NE" and (1 / C[0]) > (1 / C[1]):
            nextDirection.append("NW")
            elbow.append("NE2NW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NE" and (1 / C[0]) < (1 / C[1]):
            nextDirection.append("SE")
            elbow.append("NE2SE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SE" and (1 / C[0]) > (1 / C[1]):
            nextDirection.append("SW")
            elbow.append("SE2SW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SE" and (1 / C[0]) < (1 / C[1]):
            nextDirection.append("NE")
            elbow.append("SE2NE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SW" and (1 / C[0]) > (1 / C[1]):
            nextDirection.append("SE")
            elbow.append("SW2SE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SW" and (1 / C[0]) < (1 / C[1]):
            nextDirection.append("NW")
            elbow.append("SW2NW")
            print("___16___the next direction is", nextDirection[-1])
    else:
        if lastDirection[-1] == "NW" and C[0] > C[1]:
            nextDirection.append("NE")
            elbow.append("NW2NE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NW" and C[0] < C[1]:
            nextDirection.append("SW")
            elbow.append("NW2SW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NE" and C[0] > C[1]:
            nextDirection.append("NW")
            elbow.append("NE2NW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "NE" and C[0] < C[1]:
            nextDirection.append("SE")
            elbow.append("NE2SE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SE" and C[0] > C[1]:
            nextDirection.append("SW")
            elbow.append("SE2SW")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SE" and C[0] < C[1]:
            nextDirection.append("NE")
            elbow.append("SE2NE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SW" and C[0] > C[1]:
            nextDirection.append("SE")
            elbow.append("SW2SE")
            print("___16___the next direction is", nextDirection[-1])
        elif lastDirection[-1] == "SW" and C[0] < C[1]:
            nextDirection.append("NW")
            elbow.append("SW2NW")
            print("___16___the next direction is", nextDirection[-1])

    print("___17___the elbow is therefore", elbow[-1])
#

====================================================================================================

FOLLOWING THE CHOICE OF THE ELBOW IN edgeAnalysis(), THE ELBOW IS PLACED AND THE CENTER IS ADJUSTED FOR MIRRORING THE MODULE TO THE OTHER SIDE OF THE ELBOW, READY FOR THE NEXT WING

#
def elbowChoice(Dir):
#

First, the proper elbow has to be copied from layer 3 or 4. If the last direction is parallel to the beginning direction, the modules will be moving forward and require a module from layer 3. If perpendicular, mirroring will cause modules to move backwards, in which case they will require the elbow from layer 4.

    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
    bpy.ops.object.select_all(action="TOGGLE")
    if Dir == "SW2NW_2" or Dir == "NE2SE_2" or Dir == "NW2SW_2" or Dir == "SE2NE_2":
        bpy.context.scene.layers[4] = True
    else:
        bpy.context.scene.layers[3] = True

    bpy.data.objects[Dir].select = True
    bpy.context.scene.objects.active = bpy.data.objects[Dir]
    print(
        "___18___The selected elbow before duplication is", bpy.context.selected_objects
    )
    bpy.ops.object.duplicate()
    placedElbow.append(bpy.context.scene.objects.active)
    print(
        "___19___The active elbow is",
        bpy.context.scene.objects.active,
        "the last in the list is",
        placedElbow[-1],
    )

    bpy.ops.object.move_to_layer(
        layers=(
            True,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l

    if Dir == "SW2NW_2" or Dir == "SE2NE_2" or Dir == "NW2SW_2" or Dir == "SE2NE_2":
        bpy.context.scene.layers[4] = False
    else:
        bpy.context.scene.layers[3] = False
#

According to the chosen elbow, the cursor (and then the origin) has to be displaced differently so that the modules mirror to the same position on the other side.

    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)
    if Dir == "NW2SW" or Dir == "SW2NW":
        bpy.context.scene.cursor_location += Vector((-0.10706, 0, 0))
    elif Dir == "NE2SE" or Dir == "SE2NE":
        bpy.context.scene.cursor_location += Vector((0.10706, 0, 0))
    elif Dir == "NW2SW_2" or Dir == "SW2NW_2":
        bpy.context.scene.cursor_location += Vector((-0.28859, 0, 0))
    elif Dir == "NE2SE_2" or Dir == "SE2NE_2":
        bpy.context.scene.cursor_location += Vector((0.28859, 0, 0))
    bpy.context.area.type = "TEXT_EDITOR"

    bpy.ops.object.origin_set(type="ORIGIN_CURSOR")

    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"

    scene = bpy.context.scene
    for ob in scene.objects:
        ob.select = False
#

Selecting the last module in the last wing to prepare it for mirroring.

    stack[-1].select = True
    print(
        "___20___the selected objects after placing elbow is",
        bpy.context.selected_objects,
    )
    bpy.context.scene.objects.active = stack[-1]
    print(
        "___21___the active object after elbowchoice is",
        bpy.context.scene.objects.active,
    )
#

====================================================================================================

NOW THAT THE ELBOW HAS BEEN PREPARED AND THE CURSOR HAS BEEN MOVED TO ITS CENTER, THE NEXT WING CAN BE CREATED.

#
def nextWing():

    print(
        "___22___the elbow after executing NEXTWING is", elbow[-1]
    )  # checking that the lists are behaving like they should
    print("___23___the stack after executing NEXTWING is", stack)
#

According to the last elbow and the initial direction, the last module will be mirrored and a new wing will start in the next direction. Modules continuing North or South after bouncing off of a N-S edge will simply be mirrored around their respective extremity. Modules changing from N to S or inversely will be mirrored around their new origin, which is placed in the cursor, which was previously placed in the center of the elbow. The origin is then restored to the habitual place in the new module, by reapplying the same displacement as before.

    if (
        elbow[-1] == "NW2NE"
    ):  # here, for example, the module is flipped along the x axis, with no exterior elbow.
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_______The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_______The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )

        if startingPos == 1 or startingPos == 3:
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, 0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, 0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")

        else:
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )

        stack.append(bpy.context.scene.objects.active)
        wing("NE", 0.16, 0.16, 1)

    elif (
        elbow[-1] == "NW2SW"
    ):  # here it is flipped along the Y axis, so an elbow is added.
        if startingPos == 0 or startingPos == 2:
            elbowChoice("NW2SW")
        else:
            elbowChoice("NW2SW_2")

        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "_____24______The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "_____25______The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        bpy.ops.transform.mirror(
            constraint_axis=(True, False, False),
            constraint_orientation="GLOBAL",
            proportional="DISABLED",
            proportional_edit_falloff="SMOOTH",
            proportional_size=1,
        )

        bpy.context.area.type = "VIEW_3D"
        if startingPos == 0 or startingPos == 2:
            bpy.context.scene.cursor_location += Vector((-0.10706, 0, 0))
        else:
            bpy.context.scene.cursor_location += Vector((-0.28859, 0, 0))
        bpy.context.area.type = "TEXT_EDITOR"

        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        stack.append(bpy.context.scene.objects.active)
        stack[-2].select = False
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        wing("SW", -0.16, -0.16, 3)

    elif elbow[-1] == "NE2NW":
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "_____24______The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "_____25______The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )

        if startingPos == 0 or startingPos == 2:
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, 0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, 0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        else:
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )

        print(
            "___24___the first active object in next wing is",
            bpy.context.scene.objects.active,
        )
        stack.append(bpy.context.scene.objects.active)

        wing("NW", -0.16, 0.16, 0)

    elif elbow[-1] == "NE2SE":
        if startingPos == 1 or startingPos == 3:
            elbowChoice("NE2SE")
        else:
            elbowChoice("NE2SE_2")

        bpy.ops.object.select_all(action="TOGGLE")
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The SELECTED objects AFTER duplicating are_____________",
            bpy.context.selected_objects,
        )

        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        bpy.ops.transform.mirror(
            constraint_axis=(True, False, False),
            constraint_orientation="GLOBAL",
            proportional="DISABLED",
            proportional_edit_falloff="SMOOTH",
            proportional_size=1,
        )

        bpy.context.area.type = "VIEW_3D"

        if startingPos == 0 or startingPos == 2:
            bpy.context.scene.cursor_location += Vector((0.28859, 0, 0))
        else:
            bpy.context.scene.cursor_location += Vector((0.10706, 0, 0))

        bpy.context.area.type = "TEXT_EDITOR"

        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        stack.append(bpy.context.scene.objects.active)
        stack[-2].select = False
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        wing("SE", 0.16, -0.16, 2)

    elif elbow[-1] == "SE2SW":
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )

        if startingPos == 0 or startingPos == 2:
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
        else:
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, -0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, -0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")

        print(
            "___24___the first active object in next wing is",
            bpy.context.scene.objects.active,
        )
        stack.append(bpy.context.scene.objects.active)
        wing("SW", -0.16, -0.16, 3)

    elif elbow[-1] == "SE2NE":
        if startingPos == 0 or startingPos == 2:
            elbowChoice("SE2NE")
        else:
            elbowChoice("SE2NE_2")

        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]

        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )

        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        bpy.ops.transform.mirror(
            constraint_axis=(True, False, False),
            constraint_orientation="GLOBAL",
            proportional="DISABLED",
            proportional_edit_falloff="SMOOTH",
            proportional_size=1,
        )

        print(
            "___26___the CURSOR position BEFORE moving it is",
            bpy.context.scene.cursor_location,
        )

        bpy.context.area.type = "VIEW_3D"
        if startingPos == 0 or startingPos == 2:
            bpy.context.scene.cursor_location += Vector((0.10706, 0, 0))
        else:
            bpy.context.scene.cursor_location += Vector((0.28859, 0, 0))
        bpy.context.area.type = "TEXT_EDITOR"

        print(
            "___27___the CURSOR position AFTER moving it is",
            bpy.context.scene.cursor_location,
        )
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        stack.append(bpy.context.scene.objects.active)
        stack[-2].select = False
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        wing("NE", 0.16, 0.16, 1)

    elif elbow[-1] == "SW2SE":
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )

        if startingPos == 0 or startingPos == 2:
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, -0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
            bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )
            bpy.context.area.type = "VIEW_3D"
            bpy.context.scene.cursor_location += Vector((0, -0.453, 0))
            bpy.context.area.type = "TEXT_EDITOR"
        else:
            bpy.ops.transform.mirror(
                constraint_axis=(False, True, False),
                constraint_orientation="GLOBAL",
                proportional="DISABLED",
                proportional_edit_falloff="SMOOTH",
                proportional_size=1,
            )

        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        print(
            "___24___the first active object in next wing is",
            bpy.context.scene.objects.active,
        )
        stack.append(bpy.context.scene.objects.active)
        wing("SE", 0.16, -0.16, 2)

    elif elbow[-1] == "SW2NW":
        if startingPos == 1 or startingPos == 3:
            elbowChoice("SW2NW")
        else:
            elbowChoice("SW2NW_2")

        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        print(
            "______24_____The active object BEFORE duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.duplicate()
        print(
            "______25_____The active object AFTER duplicating is_____________",
            bpy.context.scene.objects.active,
        )
        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        bpy.ops.transform.mirror(
            constraint_axis=(True, False, False),
            constraint_orientation="GLOBAL",
            proportional="DISABLED",
            proportional_edit_falloff="SMOOTH",
            proportional_size=1,
        )

        bpy.context.area.type = "VIEW_3D"
        bpy.ops.view3d.snap_cursor_to_selected()
        if startingPos == 0 or startingPos == 2:
            bpy.context.scene.cursor_location += Vector((-0.28859, 0, 0))
        else:
            bpy.context.scene.cursor_location += Vector((-0.10706, 0, 0))
        bpy.context.area.type = "TEXT_EDITOR"

        bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
        stack.append(bpy.context.scene.objects.active)
        stack[-2].select = False
        stack[-1].select = True
        bpy.context.scene.objects.active = stack[-1]
        wing("NW", -0.16, 0.16, 0)
#

====================================================================================================

AFTER THE FINAL WING, THE END PIECE, IDENTICAL TO THE START PIECE, MUST BE ORIENTATED AND PLACED. THIS IS DONE ACCORDING TO THE DIRECTION OF THE LAST MODULE AND ITS TRAVEL ORIENTATION.

def end():
    endPiece = []
    if nextDirection[-1] == "NW":
        if nextDirection[0] in ("NW", "SE"):
            endName = "endNW"
        else:
            endName = "endSE"

    elif nextDirection[-1] == "NE":
        if nextDirection[0] in ("NE", "SW"):
            endName = "endNE"
        else:
            endName = "endSE"

    elif nextDirection[-1] == "SW":
        if nextDirection[0] in ("SW", "NE"):
            endName = "endSW"
        else:
            endName = "endNE"

    elif nextDirection[-1] == "SE":
        if nextDirection[0] in ("SE", "NW"):
            endName = "endSE"
        else:
            endName = "endNW"

    scene = bpy.context.scene
    bpy.ops.object.select_all(action="TOGGLE")
    bpy.context.scene.layers[5] = True
    object = bpy.data.objects[endName]
    object.select = True
    bpy.context.scene.objects.active = bpy.data.objects[endName]
    bpy.ops.object.duplicate()
    endPiece.append(bpy.context.scene.objects.active)
    print("_____THE ENDPIECE IS_____", endPiece[-1])
    bpy.ops.object.move_to_layer(
        layers=(
            True,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
            False,
        )
    )
    l = [False] * 20
    l[0] = True
    bpy.context.scene.layers = l
    bpy.context.scene.layers[5] = False
    bpy.context.scene.layers[0] = True
    for ob in scene.objects:
        ob.select = False
    stack[-1].select = True
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_cursor_to_selected()
    bpy.context.area.type = "TEXT_EDITOR"
    for ob in scene.objects:
        ob.select = False
    endPiece[-1].select = True
    bpy.context.area.type = "VIEW_3D"
    bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)
    bpy.context.area.type = "TEXT_EDITOR"
#
######################################## Starting the sequence

THE SEQUENCE IS STARTED WITH A RANDOM INTEGER, FROM 0 TO 3, DENOTING THE STARTING DIRECTION

startingPos = random.randint(0, 3)  # a random initial direction


print(
    "\n\n\n\n\n\n start \n\n\n\n\n\n"
)  # This is printed to seperate from previous outputs while debugging

if startingPos == 0:
    nextDirection.append("NW")
    print("the starting direction is", "NW")
elif startingPos == 1:
    nextDirection.append("NE")
    print("the starting direction is", "NE")
elif startingPos == 2:
    nextDirection.append("SE")
    print("the starting direction is", "SE")
elif startingPos == 3:
    nextDirection.append("SW")
    print("the starting direction is", "SW")

z = 8  # This counts the number of wings in the sequence

if startingPos == 0:
    START(
        "DEB1", "MOD1", "NW", -0.16, 0.16
    )  # places the starting module and the first directional module
    wing("NW", -0.16, 0.16, 0)  # builds the first wing in the randomly chosen direction
    x = 0
    while x < z:
        edgeAnalysis()  # analyses the edge when the first wing stops
        nextWing()  # builds the next wing
        x = x + 1
    moveToLayer19()  # after all wings are done, the last remaining bounding box is moved to layer 19


elif startingPos == 1:
    START("DEB2", "MOD2", "NE", 0.16, 0.16)
    wing("NE", 0.16, 0.16, 1)
    x = 0
    while x < z:
        edgeAnalysis()
        nextWing()
        x = x + 1
    moveToLayer19()


elif startingPos == 2:
    START("DEB3", "MOD3", "SE", 0.16, -0.16)
    wing("SE", 0.16, -0.16, 2)
    x = 0
    while x < z:
        edgeAnalysis()
        nextWing()
        x = x + 1
    moveToLayer19()


elif startingPos == 3:
    START("DEB4", "MOD4", "SW", -0.16, -0.16)
    wing("SW", -0.16, -0.16, 3)
    x = 0
    while x < z:
        edgeAnalysis()
        nextWing()
        x = x + 1
    moveToLayer19()
#

Finally, some modules are removed, according to the length of the last wing, so as to make place for the end module Then, the end moduled is position in place.

x = iterationCount[-1] + 1

if x < 4:
    y = 2
else:
    y = 3

while y > 0:
    stack[-1].select = True
    bpy.ops.object.delete(use_global=False)
    stack.pop()
    y = y - 1

end()

os.system(
    "cls"
)  # This clears all output from the system console. To disable, place hash tag in the beginning of this line.


a = len(stack) + 1
b = z
c = nextDirection[0]
d = nextDirection[-1]

print("\n\n\n")
print(
    "            ########################################################################################################################"
)
print(
    "            #                                                                                                                      #"
)
print(
    "                                                     HABITAT 67' - BOUND DEVELOPMENT                                                "
)
print(
    "            #                                                                                                                      #"
)
print(
    "                              This conjugation of the Habitat 67' vocabulary has",
    a,
    "modules, in",
    b,
    "wings                          ",
)
print(
    "            #                                                                                                                      #"
)
print(
    "                                       The first direction was",
    c,
    "and the last direction was",
    d,
    "                                  ",
)
print(
    "            #                                                                                                                      #"
)
print(
    "            ########################################################################################################################"
)